package cn.cas.mango.util.RequestUtil;

import cn.cas.mango.dto.param.MaterialParam;
import cn.cas.mango.service.ConfigService;
import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.query_dsl.*;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.indices.AnalyzeRequest;
import co.elastic.clients.elasticsearch.indices.AnalyzeResponse;
import co.elastic.clients.elasticsearch.indices.analyze.AnalyzeToken;
import co.elastic.clients.json.JsonData;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
public class BuildMaterialRequest {

  @Autowired
  private ConfigService config;

  public final String[] includesArray = new String[]{
    "material_title", "id",
    "material_date", "material_type", "material_security", "material_category",
    "material_content", "material_source"
  };

  public final String[] baseArray = new String[]{
    "id",
    "material_date", "material_type", "material_security", "material_category",
    "material_source"
  };

  public SearchRequest.Builder buildMaterialRequest(MaterialParam materialParam, boolean filterUid) {
    List<String> fields = new ArrayList<>();
    Map<String, HighlightField> map = new HashMap<>();
    String queryField = materialParam.getQueryField();
    String queryStr = materialParam.getQueryStr().trim();
    String[] splitQueryStr = queryStr.split("\\s+");


    List<String> includes = new ArrayList<>(Arrays.asList(baseArray));
    includes.add("material_content");
    includes.add("material_title");


    MultiMatchQuery multiMatchQuery = null;
    MatchPhrasePrefixQuery matchPhrasePrefixQuery = null;
    // 添加should 让更接近搜索的排在前面
    MultiMatchQuery multiShouldMatchQuery = null;
    MatchQuery matchQuery = null;

    BoolQuery.Builder tempBool = new BoolQuery.Builder();
    setFields(queryField, fields);
    if (queryField.equals("all")) {
      // true为模糊匹配，false为精确匹配
      if (materialParam.getSearchType().equalsIgnoreCase("true")) {
        for (String query : splitQueryStr) {
          multiMatchQuery = MultiMatchQuery.of(
            v -> v.query(query).fields(fields).type(TextQueryType.PhrasePrefix)
          );
          MultiMatchQuery finalMultiMatchQuery = multiMatchQuery;
          tempBool.must(m -> m.multiMatch(finalMultiMatchQuery));
        }

      } else {
        if (materialParam.getDegree() == null) {
          for (String query : splitQueryStr) {
            multiMatchQuery = MultiMatchQuery.of(
                v -> v.query(query).fields(fields).type(TextQueryType.BestFields));
            MultiMatchQuery finalMultiMatchQuery = multiMatchQuery;
            tempBool.must(m -> m.multiMatch(finalMultiMatchQuery));
          }
        } else {
          for (String query : splitQueryStr) {
            multiMatchQuery = MultiMatchQuery.of(
                v -> v.query(query).fields(fields).type(TextQueryType.Phrase)
//                    .minimumShouldMatch(config.getFuzzyValue())
            );
            MultiMatchQuery finalMultiMatchQuery = multiMatchQuery;
            tempBool.should(m -> m.multiMatch(finalMultiMatchQuery));
          }
          tempBool.minimumShouldMatch(String.format("%d%%", (int)(materialParam.getDegree() * 100)));
        }

      }
    } else {
      if (materialParam.getSearchType().equalsIgnoreCase("true")) {
        for (String query : splitQueryStr) {
          matchPhrasePrefixQuery = MatchPhrasePrefixQuery.of(v -> v.query(query).field(fields.get(0)));
          MatchPhrasePrefixQuery finalMatchPhraseQuery = matchPhrasePrefixQuery;
          tempBool.must(m -> m.matchPhrasePrefix(finalMatchPhraseQuery));
        }
      } else {

        for (String query : splitQueryStr) {
          matchQuery = MatchQuery.of(v -> v.query(query).field(fields.get(0)));

          MatchQuery finalMatchQuery = matchQuery;
          tempBool.must(m -> m.match(finalMatchQuery));
        }
      }
    }

    SearchRequest.Builder builder = new SearchRequest.Builder();
    RangeQuery rq;

    // 起止日期的检索逻辑
    if (Strings.isNotBlank(materialParam.getStartDate()) && Strings.isNotBlank(
      materialParam.getEndDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").gte(JsonData.of(materialParam.getStartDate()))
          .lte(JsonData.of(materialParam.getEndDate())));
    } else if (Strings.isNotBlank(materialParam.getStartDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").gte(JsonData.of(materialParam.getStartDate())));
    } else if (Strings.isNotBlank(materialParam.getEndDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").lte(JsonData.of(materialParam.getEndDate())));
    } else {
      rq = null;
    }


    BoolQuery tempBoolQuery = tempBool.build();
    setNormal(builder, materialParam, queryStr, rq, tempBoolQuery, includes, filterUid);

    if (materialParam.getPageSize() > 0) {

      Highlight.Builder h = new Highlight.Builder();
      SearchRequest.Builder tempBuilder = new SearchRequest.Builder();
      setNormal(tempBuilder, materialParam, queryStr, rq, tempBoolQuery, includes, filterUid);


      List<Query> temp = tempBuilder.build().query().bool().must();
      if (materialParam.getSearchType().equals("true") && temp != null && !temp.isEmpty()) {
        Query query = temp.get(0);
        List<Query> must = query.bool().must();
        List<Query> ret = new ArrayList<>();

        if (fields.size() == 2) {
          must.forEach(x -> {
            MultiMatchQuery multiMatchQuery1 = x.multiMatch();

            MultiMatchQuery.Builder b = new MultiMatchQuery.Builder();
            b.query(multiMatchQuery1.query());
            b.fields(
              new ArrayList<String>() {{
                add("material_content");
                add("material_title");
              }});
            b.type(TextQueryType.Phrase);
            ret.add(b.build()._toQuery());
          });
        } else {
          must.forEach(x -> {
            MatchPhrasePrefixQuery matchPhrasePrefixQuery1 = x.matchPhrasePrefix();
            MultiMatchQuery.Builder b = new MultiMatchQuery.Builder();
            b.query(matchPhrasePrefixQuery1.query());
            b.fields(
              new ArrayList<String>() {{
                add("material_content");
                add("material_title");
              }});
            b.type(TextQueryType.Phrase);
            ret.add(b.build()._toQuery());
          });
        }

        Query.Builder highlightQuery = new Query.Builder();
        highlightQuery.bool(b -> b.must(ret));
        setHighlight(queryField, map, highlightQuery);
      } else {
        setHighlight(queryField, map);
      }
      h.fields(map);
      builder.highlight(h.build());
    }
    return builder;
  }

  public SearchRequest.Builder buildMaterialQueryStrRequest(MaterialParam materialParam, boolean filterUid) {
    log.info("buildMaterialQueryStrRequest");
    List<String> fields = new ArrayList<>();
    String queryField = materialParam.getQueryField();
    Map<String, HighlightField> map = new HashMap<>();
    List<String> includes = Arrays.asList(includesArray);
    setFields(queryField, fields);
    setHighlight(queryField, map);
    RangeQuery rq;
    if (Strings.isNotBlank(materialParam.getStartDate()) && Strings.isNotBlank(
      materialParam.getEndDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").gte(JsonData.of(materialParam.getStartDate()))
          .lte(JsonData.of(materialParam.getEndDate())));
    } else if (Strings.isNotBlank(materialParam.getStartDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").gte(JsonData.of(materialParam.getStartDate())));
    } else if (Strings.isNotBlank(materialParam.getEndDate())) {
      rq = RangeQuery.of(
        r -> r.field("material_date").lte(JsonData.of(materialParam.getEndDate())));
    } else {
      rq = null;
    }
    SearchRequest.Builder builder = new SearchRequest.Builder();
    builder.index(config.getMaterialDepartment().get(materialParam.getDepartment()))
      .query(
        query -> query.bool(
          bool -> {
            String materialSources = materialParam.getMaterialSources();
            if (Strings.isNotBlank(materialSources)) {
              if (materialSources.contains("_")) {
                String[] split = materialSources.split("_");
                if (split.length == 2) {
                  TermQuery tq1 = TermQuery.of(t -> t.field("material_source").value(split[1]));
                  bool.filter(f -> f.term(tq1));
                  TermQuery tq2 = TermQuery.of(t -> t.field("material_category").value(split[0]));
                  bool.filter(f->f.term(tq2));
                }
              }
            }

            List<FieldValue> materialTypesArray = materialParam.getMaterialTypesArray();
            if (materialTypesArray != null && !materialTypesArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                  t -> t.field("material_type").terms(x -> x.value(materialTypesArray)));
              bool = bool.filter(f -> f.terms(sq1));
            }

            List<FieldValue> materialSecurityArray = materialParam.getSecurityArray();
            if (filterUid && materialSecurityArray != null && !materialSecurityArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                  t -> t.field("material_security_id").terms(x -> x.value(materialSecurityArray)));
              bool = bool.filter(f -> f.terms(sq1));
            }

            List<FieldValue> materialUidArray = materialParam.getUidArray();
            if (filterUid && materialUidArray != null && !materialUidArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                  t -> t.field("available_uid").terms(x -> x.value(materialUidArray)));
              bool = bool.filter(f -> f.terms(sq1));
            }
            if (rq != null) {
              return bool.must(
                must -> must.queryString(
                  queryString -> queryString.query(
                    materialParam.getQueryStr())
                    .fields(
                      fields
                    ).boost(config.getBoost()).analyzer(config.getAnalyzer())
                )
              )
                .filter(f -> f.range(rq));
            } else {
              return bool.must(
                must -> must.queryString(
                  queryString -> queryString.query(
                    materialParam.getQueryStr())
                    .fields(
                      fields
                    ).boost(config.getBoost()).analyzer(config.getAnalyzer())
                )
              );
            }

          }
        )
      )
      .source(source -> source.filter(f -> f.includes(includes)))
      .from((materialParam.getCurrentPage() - 1) * materialParam.getPageSize())
      .size(filterUid ? materialParam.getPageSize(): 0)
      .trackTotalHits(c -> c.count(config.getHitsCount()));

    if (materialParam.getPageSize() > 0) {
      builder.highlight(hi -> hi.fields(map));
    }
    return builder;
  }

  private void setNormal(SearchRequest.Builder builder, MaterialParam materialParam, String queryStr,
                         RangeQuery rq, BoolQuery tempBoolQuery, List<String> includes, boolean filterUid) {
    builder
      .index(config.getMaterialDepartment().get(materialParam.getDepartment()))
      .query(
        QueryBuilders.bool(bool ->
          {
            BoolQuery.Builder b;
            if (Strings.isBlank(queryStr)) {
              if (rq != null) {
                b = bool.filter(f -> f.range(rq));
              } else {
                b = bool;
              }
            } else {
              if (rq != null) {
                b = bool.must(must -> must.bool(tempBoolQuery))
                  .filter(f -> f.range(rq));
              } else {
                b = bool.must(must -> must.bool(tempBoolQuery));
              }
            }

            String materialSources = materialParam.getMaterialSources();
            if (Strings.isNotBlank(materialSources)) {
              if (materialSources.contains("_")) {
                String[] split = materialSources.split("_");
                if (split.length == 2) {
                  TermQuery tq1 = TermQuery.of(t -> t.field("material_source").value(split[1]));
                  b.filter(f -> f.term(tq1));
                  TermQuery tq2 = TermQuery.of(t -> t.field("material_category").value(split[0]));
                  b.filter(f->f.term(tq2));
                }
              }
            }

            List<FieldValue> materialTypesArray = materialParam.getMaterialTypesArray();
            if (materialTypesArray != null && !materialTypesArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                t -> t.field("material_type").terms(x -> x.value(materialTypesArray)));
              b = b.filter(f -> f.terms(sq1));
            }

            List<FieldValue> materialSecurityArray = materialParam.getSecurityArray();
            if (materialSecurityArray != null && !materialSecurityArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                t -> t.field("material_security_id").terms(x -> x.value(materialSecurityArray)));
              b = b.filter(f -> f.terms(sq1));
            }

            List<FieldValue> materialUidArray = materialParam.getUidArray();
            if (filterUid && materialUidArray != null && !materialUidArray.isEmpty()) {
              TermsQuery sq1 = TermsQuery.of(
                t -> t.field("available_uid").terms(x -> x.value(materialUidArray)));
              b = b.filter(f -> f.terms(sq1));
            }
            return b;
          }
        ))
      .from((materialParam.getCurrentPage() - 1) * materialParam.getPageSize())
      .size(filterUid ? materialParam.getPageSize(): 0)
      .trackTotalHits(c -> c.count(config.getHitsCount()))
      .source(source -> source.filter(f -> f.includes(includes)));

  }

  private void setFields(String queryField,
                         List<String> fields) {
    switch (queryField) {
      case "content":
        fields.add("material_content");
        break;
      case "title":
        fields.add("material_title");
        break;
      default:
        fields.add("material_title");
        fields.add("material_content");
        break;
    }
  }

  private void setHighlight(String queryField,
                            Map<String, HighlightField> map) {

    HighlightField contentField = HighlightField.of(
      f -> f.preTags("<span class=\"highlight\">").postTags("</span>").numberOfFragments(3)
        .fragmentSize(config.getFragmentSize()).boundaryMaxScan(30));
    HighlightField titleField = HighlightField.of(
      f -> f.preTags("<span class=\"highlight\">").postTags("</span>").numberOfFragments(2));
    switch (queryField) {
      case "content":
        map.put("material_content", contentField);
        break;
      case "title":
        map.put("material_title", titleField);
        break;
      default:
        map.put("material_title" , titleField);
        map.put("material_content", contentField);
        break;
    }
  }

  private void setHighlight(String queryField,
                            Map<String, HighlightField> map,
                            Query.Builder highlightBuilder) {
    Query build = highlightBuilder.build();
    HighlightField contentField = HighlightField.of(
      f -> f.preTags("<span class=\"highlight\">").postTags("</span>").numberOfFragments(5)
        .fragmentSize(config.getFragmentSize()).boundaryMaxScan(10).highlightQuery(build));
    HighlightField titleField = HighlightField.of(
      f -> f.preTags("<span class=\"highlight\">").postTags("</span>").numberOfFragments(2)
        .highlightQuery(build));
    switch (queryField) {
      case "content":
        map.put("material_content", contentField);
        break;
      case "title":
        map.put("material_title", titleField);
        break;
      default:
        map.put("material_title", titleField);
        map.put("material_content" , contentField);
        break;
    }
  }


}
